package system.back;

import java.util.*;
import java.io.*;

import system.struct.AggEntry;
import system.struct.LogEntry;
import system.util.Inflector;
import system.util.Timer;

/***
 * Loader of system.
 * 
 * <p>
 * This implementation can load all information needed based on <tt>HashMap</tt>
 * and <tt>ArrayList</tt>, including <i>Instances</i>, <i>Concepts</i>,
 * <i>Search Log List</i> and <i> Aggregation List</i>.
 * 
 * @author Jack
 * 
 */
public class Loader {
	/**
	 * path of the file that contains <i>Instance List</i>
	 */
	private String instancePath = "pro_typicality" + File.separator
			+ "ProbaseInstance_Filtered.ltf";
	/**
	 * path of the file that contains <i>Concept List</i>
	 */
	private String conceptPath = "pro_typicality" + File.separator
			+ "ProbaseClass_Filtered.ltf";
	/**
	 * path of the file that contains <i>Selected Search Log</i>
	 */
	private String logPath = "sample.txt";
	/**
	 * path of the file that contains <i>Aggregation of Search Log</i> according
	 * to <i>Probase Concepts</i>
	 */
	private String aggPath = "agg.txt";

	/**
	 * Mapping <i>Concept Name</i> to its <i>Concept Index</i>
	 */
	HashMap<String, Integer> cnptMap = new HashMap<String, Integer>();
	/**
	 * Mapping <i>Instance Name</i> to its <i>Instance Index</i>
	 */
	HashMap<String, Integer> instMap = new HashMap<String, Integer>();
	/**
	 * List of <i>Concepts</i>, <i>Concept Name</i> can be accessed by its
	 * <i>Concept Index</i>
	 */
	ArrayList<String> cnptList = new ArrayList<String>();
	/**
	 * List of <i>Instances</i>, <i>Instance Name</i> can be accessed by its
	 * <i>Instance Name</i>
	 */
	ArrayList<String> instList = new ArrayList<String>();
	/**
	 * List of <i>Search Log Entry</i>, <i>Log Entry</i> can be accessed by its
	 * <i>Log Index</i>
	 */
	ArrayList<LogEntry> logList = new ArrayList<LogEntry>();
	/**
	 * <i>Aggregation of Concept</i>
	 * <p>
	 * Mapping each <i>Concept</i> to its matched <i>Log Entry</i> list
	 */
	HashMap<Integer, ArrayList<AggEntry>> aggMap = new HashMap<Integer, ArrayList<AggEntry>>();

	/**
	 * Constructs an default <tt>Loader</tt>
	 */
	public Loader() {
		cnptList.add(null);
		instList.add(null);
		logList.add(null);
	}

	/**
	 * Constructs an <tt>Loader</tt> according to given paths
	 * 
	 * @param insPath
	 *            path of <i>Probase Instance List</i> file
	 * @param conPath
	 *            path of <i>Probase Concepts List</i> file
	 */
	public Loader(String insPath, String conPath) {
		instancePath = insPath;
		conceptPath = conPath;
	}

	/**
	 * Returns <tt>true</tt> if <i>Probase</i> has this <i>Instance</i>
	 * 
	 * @param inst
	 *            <i>Instance</i> name
	 * @return <tt>true</tt> if <i>Probase</i> has this <i>Instance</i>
	 */
	public boolean hasInst(String inst) {
		return instMap.containsKey(inst);
	}

	/**
	 * Returns <tt>true</tt> if <i>Probase</i> has this <i>Concept</i>
	 * 
	 * @param cnpt
	 *            <i>Concept</i> name
	 * @return <tt>true</tt> if <i>Probase</i> has this <i>Concept</i>
	 */
	public boolean hasCnpt(String cnpt) {
		return cnptMap.containsKey(cnpt);
	}

	/**
	 * Returns <i>Instance Index</i> of given <i>Instance Name</i>
	 * 
	 * @param instName
	 *            <i>Instance Name</i>
	 * @return <i>Instance Index</i> of given <i>Instance Name</i>
	 */
	public int indexOfInst(String instName) {
		Integer ans = instMap.get(instName);
		return (ans != null) ? ans : 0;
	}

	/**
	 * Returns <i>Concept Index</i> of given <i>Concept Name</i>
	 * 
	 * @param cnptName
	 *            <i>Concept Name</i>
	 * @return <i>Concept Index</i> of given <i>Concept Name</i>
	 */
	public int indexOfCnpt(String cnptName) {
		Integer ans = cnptMap.get(cnptName);
		return (ans != null) ? ans : 0;
	}

	public LogEntry getLogEntry(int index) {
		if (index > 0 && index < logList.size())
			return logList.get(index);
		else
			return null;
	}

	/**
	 * Returns <i>Instance Name</i> of given <i>Instance Index</i>
	 * 
	 * @param index
	 *            <i>Instance Index</i>
	 * @return <i>Instance Name</i> of given <i>Instance Index</i>
	 */
	public String getInst(int index) {
		if (index > 0 && index < instList.size())
			return instList.get(index);
		else
			return null;
	}

	/**
	 * Returns <i>Concept Name</i> of given <i>Concept Index</i>
	 * 
	 * @param index
	 *            <i>Concept Index</i>
	 * @return <i>Concept Name</i> of given <i>Concept Index</i>
	 */
	public String getCnpt(int index) {
		if (index > 0 && index < cnptList.size())
			return cnptList.get(index);
		else
			return null;
	}

	/**
	 * Returns <i>Aggregation List</i> according to given <i>Concept Index</i>
	 * 
	 * @param cIndex
	 *            <i>Concept Index</i>
	 * @return <i>Aggregation List</i> of given <i>Concept</i>
	 */
	public ArrayList<AggEntry> getAggList(int cIndex) {
		return aggMap.get(cIndex);
	}

	public void loadAll() {
		loadInstance();
		loadLog();
		loadAggData();
		loadConcept();
	}

	private void mergeAggCnpt(int cIndex1, int cIndex2) {
		ArrayList<AggEntry> ar1, ar2;
		ar1 = aggMap.get(cIndex1);
		ar2 = aggMap.get(cIndex2);
		ar1 = (ar1 != null) ? ar1 : new ArrayList<AggEntry>();
		ar2 = (ar2 != null) ? ar2 : new ArrayList<AggEntry>();
		ar1.addAll(ar2);
		aggMap.put(cIndex1, ar1);
		aggMap.put(cIndex2, ar1);
	}

	/**
	 * Load <i>Concept</i> into this <tt>Loader</tt>
	 */
	public void loadConcept() {
		this.readConcept(conceptPath);
	}

	private void readConcept(String conPath) {
		try {
			FileInputStream fis = new FileInputStream(conPath);
			DataInputStream dis = new DataInputStream(fis);
			BufferedReader br = new BufferedReader(new InputStreamReader(dis));
			String line = br.readLine();
			Inflector inf = Inflector.getInstance();
			int count = 1, temp;
			while (line != null && !line.equals("")) {
				line = inf.pluralize(line);
				if (cnptMap.containsKey(line)) {
					/* plural form has appeared, need to merge two aggregation
					 * list
					 */
					temp = cnptMap.get(line);
					mergeAggCnpt(temp, count);
				} else {
					cnptMap.put(line, count);
				}
				cnptList.add(line);
				if (count % 1000000 == 0) {
					System.out.print(count + " c: ");
					Timer.time();
				}
				line = br.readLine();
				count++;
			}
			br.close();
			System.out.println(cnptMap.size());
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	/**
	 * Load <i>Instances</i> into this <tt>Loader</tt>
	 */
	public void loadInstance() {
		this.readInstance(instancePath);
	}

	private void readInstance(String insPath) {
		try {
			FileInputStream fis = new FileInputStream(insPath);
			DataInputStream dis = new DataInputStream(fis);
			BufferedReader br = new BufferedReader(new InputStreamReader(dis));
			String line = br.readLine();
			int count = 1;
			while (line != null && !line.equals("")) {
				instMap.put(line, count);
				instList.add(line);
				if (count % 1000000 == 0) {
					System.out.print(count + " i: ");
					Timer.time();
				}
				line = br.readLine();
				count++;
			}
			br.close();
			System.out.println(instMap.size());
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	/**
	 * Load <i>Search Log Entries</i> into this <tt>Loader</tt>
	 */
	public void loadLog() {
		this.readLog(logPath);
	}

	private void readLog(String logPath) {
		try {
			FileInputStream fis = new FileInputStream(logPath);
			DataInputStream dis = new DataInputStream(fis);
			BufferedReader br = new BufferedReader(new InputStreamReader(dis));
			String line = br.readLine();
			int count = 1;
			String[] tmp;
			int freq;
			while (line != null && !line.equals("")) {
				tmp = line.toLowerCase().split("\t");
				freq = Integer.parseInt(tmp[1]);
				logList.add(new LogEntry(count, freq, tmp[0]));
				if (count % 100000 == 0) {
					System.out.print(count + " log: ");
					Timer.time();
				}
				line = br.readLine();
				count++;
			}
			br.close();
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	/**
	 * Load <i>Aggregation</i> information into this <tt>Loader</tt>
	 */
	public void loadAggData() {
		this.readAggData(aggPath);
	}

	private void readAggData(String aggPath) {
		try {
			FileInputStream fis = new FileInputStream(aggPath);
			DataInputStream dis = new DataInputStream(fis);
			BufferedReader br = new BufferedReader(new InputStreamReader(dis));
			String line = br.readLine();
			int count = 1;
			String[] tmp;
			int qIndex, iIndex;
			double typicality;
			int cIndex, num;
			while (line != null && !line.equals("")) {
				tmp = line.split("\t");
				cIndex = Integer.parseInt(tmp[0]);
				num = Integer.parseInt(tmp[1]);
				if (!aggMap.containsKey(cIndex))
					aggMap.put(cIndex, new ArrayList<AggEntry>());
				ArrayList<AggEntry> list = aggMap.get(cIndex);
				for (int i = 0; i < num; i++) {
					line = br.readLine();
					tmp = line.split("\t");
					qIndex = Integer.parseInt(tmp[0]);
					iIndex = Integer.parseInt(tmp[2]);
					typicality = Double.parseDouble(tmp[3]);
					list.add(new AggEntry(qIndex, iIndex, typicality));
				}
				if (count % 100000 == 0) {
					System.out.print(count + " agg: ");
					Timer.time();
				}
				line = br.readLine();
				count++;
			}
			br.close();
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	/**
	 * Returns information of this <tt>Loader</tt>
	 * 
	 * @return <tt>String</tt> of this <tt>Loader</tt>'s information
	 */
	public String info() {
		String ans = "======= Information of the Parser ==========\n";
		ans += "1. Instance number: " + instMap.size() + "\n";
		ans += "2. Concept  number: " + cnptMap.size() + "\n";
		ans += "3. log      number: " + (logList.size() - 1) + "\n";
		ans += "4. agg data number: " + aggMap.size() + "\n";
		ans += "============================================";
		return ans;
	}

	public String toString() {
		return this.info();
	}
}
